package storyteller;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;

public class SmilWriter
{
  
  //`````````````````````
  // STATIC VARIABLES
  //`````````````````````
  
  private static final String DEFAULT_DURATION_TEXT = "3 sec";
  private static final int DEFAULT_DURATION = 3;
  private static final String INDENTATION = "  ";
  
  //`````````````````````
  // MEMBER VARIABLES
  //`````````````````````

  private String _outputFilename;
  private FlashAnalyzer _analyzer;
  private boolean _isInParallel;
  private boolean _isInAudio;
  private String _music;
  private int _musicDuration;
  private FlashAction _currentAction;
  private int _currentIndentation;
  
  //`````````````````````
  // CONSTRUCTOR
  //`````````````````````
  
  public SmilWriter(String outputFilename)
  {
    init(outputFilename);
  }
  
  private void init(String outputFilename)
  {
    _outputFilename = outputFilename;
    _analyzer = new FlashAnalyzer(null,false);
    _isInParallel = false;
    _isInAudio = false;
    _music = null;
    _musicDuration = 0;
    _currentAction = null;
    _currentIndentation = 0;
  }
  
  //`````````````````````
  // INTERFACE
  //`````````````````````
  
  public void parseContent(String content)
  {
    _analyzer.analyzeAll(content, null);
  }  
  
  public FlashAction[] parse(String filename) 
  {
    return _analyzer.parse(filename);
  }  
  
  @SuppressWarnings("unchecked")
  public void save() 
  {
    try 
    {
      OutputStream fout = new FileOutputStream(_outputFilename);
      OutputStream bout = new BufferedOutputStream(fout);
      OutputStreamWriter out = new OutputStreamWriter(bout);
      
      indent(out,"<smil>");
      indent(out,"<head>");
      indent(out,"<layout>");
      writeln(out,toXml("root-layout",new String[] {"id","window"}));
      writeln(out,toXml("region",new String[] {"id","caption","left","10","top","10","right","10","z-index","1","background-color","white"}));
      writeln(out,toXml("region",new String[] {"id","video","left","10","top","60","right","10","z-index","2"}));
      outdent(out,"</layout>");
      outdent(out,"</head>");
      indent(out,"<body>");
      indent(out,"<seq>");
      
      while ((_currentAction = _analyzer.nextAction()) != null) 
      {
        if (_currentAction.Type == FlashActionType.Text)
        {
          insertTextFrame(out,(String)_currentAction.Data);
        }
        else if (_currentAction.Type == FlashActionType.MultiText)
        {
          insertMultiLineTextFrame(out,(ArrayList<String>)_currentAction.Data);
        }
        else if (_currentAction.Type == FlashActionType.Image)
        {
          insertImageFrame(out, (String)_currentAction.Data);
        }
        else if (_currentAction.Type == FlashActionType.Label)
        {
          overlayLabel(out, (String)_currentAction.Data);
        }
        else if (_currentAction.Type == FlashActionType.MultiLabel)
        {
          overlayMultiLabel(out, (ArrayList<String>)_currentAction.Data);
        }
        else if (_currentAction.Type == FlashActionType.AudioPlay)
        {
          prepareAudio(out,(String)_currentAction.Data);
        }
        else if (_currentAction.Type == FlashActionType.AudioStop)
        {
          endAudioAsRequired(out);
        }
        trackAudioAsRequired(_currentAction.Type);
      }
      
      
      endAudioAsRequired(out);
      endParallelAsRequired(out);
      outdent(out,"</seq>");
      outdent(out,"</body>");
      outdent(out,"</smil>");
      out.flush();
      out.close();

    } 
    catch (Exception e) 
    {
      throw new RuntimeException("Error writing ["+ _outputFilename +"] to disk",e);
    }
  }


  //`````````````````````
  // PRIVATE METHODS
  //`````````````````````  

  private void prepareAudio(OutputStreamWriter out, String audio) throws IOException
  {
    endAudioAsRequired(out);
    _music = audio;
    _isInAudio = true;
    _musicDuration = 0;
    indent(out,"<par>");
    indent(out,"<seq>");
  }
  
  private void endAudioAsRequired(OutputStreamWriter out) throws IOException
  {
    if (_isInAudio)
    {
      outdent(out,"</seq>");
      writeln(out,toXml("audio",new String[]{"src",_music,"dur",(_musicDuration) + " sec"}));
      outdent(out,"</par>");
      _isInAudio = false;
    }
  }
  
  private void trackAudioAsRequired(FlashActionType type)
  {
    if (_isInAudio && !_isInParallel & type != FlashActionType.AudioPlay)
    {
      _musicDuration += FlashAnalyzer.slideDuration(currentDuration(), DEFAULT_DURATION);
    }
  }
  
  private void insertTextFrame(OutputStreamWriter out, String text) throws IOException
  {
    writeln(out,toXml("text",new String[]{"region","caption","src","data:" + text,"dur",currentDuration()}));
  }
  
  private void insertMultiLineTextFrame(OutputStreamWriter out, ArrayList<String> data) throws IOException
  {
    if (data.size() == 0)
    {
      insertTextFrame(out,"");
    }
    else
    {
      StringBuilder text = new StringBuilder(data.get(0));
      for(int i=1; i<data.size(); i++)
      {
        text.append("%0D%0A");
        text.append(data.get(i));
      }
      insertTextFrame(out,text.toString());
    }
  }
  
  private void insertImageFrame(OutputStreamWriter out, String image) throws IOException
  {
    writeln(out,toXml("img",new String[]{"src",image,"region","video","dur",currentDuration()}));
    endParallelAsRequired(out);
  }
  
  
  private void overlayLabel(OutputStreamWriter out, String text) throws IOException
  {
    startParallelAsRequired(out);
    insertTextFrame(out,text);
  }
  
  private void overlayMultiLabel(OutputStreamWriter out, ArrayList<String> data) throws IOException
  {
    startParallelAsRequired(out);
    insertMultiLineTextFrame(out, data);
  }
  
  private void startParallelAsRequired(OutputStreamWriter out) throws IOException
  {
    if (!_isInParallel)
    {
      indent(out,"<par>");
      _isInParallel = true;
    }
  }  
  
  private void endParallelAsRequired(OutputStreamWriter out) throws IOException
  {
    if (_isInParallel)
    {
      outdent(out,"</par>");
      _isInParallel = false;
    }
  }
  
  private void indent(OutputStreamWriter out, String output) throws IOException
  {
    writeln(out,output);
    _currentIndentation += 1;
  }

  private void outdent(OutputStreamWriter out, String output) throws IOException
  {
    _currentIndentation -= 1;
    writeln(out,output);
  }  
  
  private void writeln(OutputStreamWriter out, String output) throws IOException
  {
    for (int i=0; i<_currentIndentation; i++)
    {
      out.write(INDENTATION);
    }
    out.write(output);
    out.write("\n");
  }
  
  private String toXml(String label, String[] allData)
  {
    StringBuilder builder = new StringBuilder();
    builder.append("<" + label + " ");
    for(int i=0; i<allData.length; i+=2)
    {
      String key = allData[i];
      String value = allData[i+1];
      builder.append(key + "=\""+ value +"\" ");
    }
    builder.append("/>");
    return builder.toString();
  }
  
  private String currentDuration()
  {
    return _currentAction.propertyOf("duration",DEFAULT_DURATION_TEXT);
  }

}
